package com.karolak.client.fragments;

import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.Format;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import com.karolak.bean.PropertyComparator;
import com.karolak.client.PropertyTableCellRenderer;
import com.karolak.client.PropertyTableCellRenderer.AlienProperty;

public class ObjectTablePane<T> extends TablePane {

	private static final long serialVersionUID = 1L;

	private Class<T> objectClazz;
	private Map<String, String> columnPropertyMapping;
	private Map<String, Boolean> sortOrder = new HashMap<String, Boolean>();
	private Map<String, Format> columnFormatMapping = new HashMap<String, Format>();

	private long dataSelectedTime;

	public ObjectTablePane(Class<T> objectClazz, Map<String, String> columnPropertyMapping) {
		this(objectClazz, columnPropertyMapping, null);
	}

	public ObjectTablePane(Class<T> objectClazz, Map<String, String> columnPropertyMapping, Map<String, Format> columnFormatMapping) {
		super();
		this.columnPropertyMapping = columnPropertyMapping;
		this.objectClazz = objectClazz;
		if (columnFormatMapping != null) {
			this.columnFormatMapping = columnFormatMapping;
		}
		this.columnFormatMapping = columnFormatMapping;
		if (getColumnPropertyMapping() != null) {
			setHeaders(getColumnPropertyMapping().keySet().toArray(new String[] {}));
			setCellRenderer(createTableCellRenderer());
			getTable().getTableHeader().addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					sortByColumn(e);
				}
			});
		}
	}

	protected TableCellRenderer createTableCellRenderer() {
		return new PropertyTableCellRenderer<T>(getObjectClazz(), getColumnPropertyMapping(), getColumnFormatMapping());
	}

	public void sortByColumn(MouseEvent mouseEvent) {
		TableColumnModel columnModel = getTable().getColumnModel();
		int columnIx = columnModel.getColumnIndexAtX(mouseEvent.getX());
		TableColumn column = columnModel.getColumn(columnIx);
		String columnName = column.getHeaderValue().toString();
		Boolean ascending = getSortOrder().get(columnName);
		if (ascending == null) {
			ascending = true;
		}
		ascending = !ascending;
		sortByColumn(columnName, ascending);
		getSortOrder().put(columnName, ascending);
	}

	public void sortByColumn(String columnName) {
		Boolean ascending = getSortOrder().get(columnName);
		if (ascending == null) {
			ascending = true;
		}
		sortByColumn(columnName, ascending);
	}

	public void sortByColumn(String columnName, boolean ascending) {

		String propertyName = getPropertyName(columnName);

		if (propertyName == null || AlienProperty.getByName(propertyName) != null) {
			return;
		}

		Comparator<T> comparator = createComparator(propertyName);
		List<T> list = getObjectData();
		Collections.sort(list, comparator);

		if (!ascending) {
			Collections.reverse(list);
		}

		Object[] selectedValues = getSelectedValues();

		for (int i = 0; i < list.size(); i++) {
			setObjectDataRow(list.get(i), i);
		}

		// keep selection
		ListSelectionListener[] listeners = removeSelectionListener();

		getTable().clearSelection();

		if (selectedValues != null && selectedValues.length > 0) {
			for (Object o : selectedValues) {
				setSelectedObjectDataRow(o);
			}
		}

		addSelectionListener(listeners);

		// make selection visible
		int ixMin = getTable().getSelectionModel().getMinSelectionIndex();
		int ixMax = getTable().getSelectionModel().getMaxSelectionIndex();

		if (ixMin > -1) {
			Rectangle rMin = getTable().getCellRect(ixMin, 0, true);
			if (ixMin != ixMax) {
				Rectangle rMax = getTable().getCellRect(ixMax, 0, true);
				rMin.add(rMax);
				Rectangle r = getTable().getVisibleRect();
				if (rMin.getHeight() > r.getHeight()) {
					rMin.setSize((int) rMin.getWidth(), (int) r.getHeight());
				}
			}
			getTable().scrollRectToVisible(rMin);
		}
	}

	public void reloadObjectData(List<T> data) {
		removeData();
		addObjectData(data);
	}

	public void reloadObjectData(List<T> data, long dataSelectedTime) {
		removeData();
		addObjectData(data);
		setDataSelectedTime(dataSelectedTime);
	}

	public void reloadObjectData(List<T> data, long dataSelectedTime, String sortByColumnName) {
		reloadObjectData(data, dataSelectedTime);
		sortByColumn(sortByColumnName);
	}

	public void setColumnSize(Map<String, Integer> columnWidths, int defaultWidth) {
		if (getTable() == null || columnWidths == null || columnWidths.isEmpty()) {
			return;
		}
		DefaultTableColumnModel cModel = (DefaultTableColumnModel) getTable().getColumnModel();
		for (int i = 0; i < cModel.getColumnCount(); i++) {
			TableColumn column = cModel.getColumn(i);
			Object header = column.getHeaderValue();
			Integer width = columnWidths.get(header);
			if (width != null) {
				column.setPreferredWidth(width);
			} else if (defaultWidth > 0) {
				column.setPreferredWidth(defaultWidth);
			}
		}
	}

	public void setColumnSize(Map<String, Integer> columnWidths) {
		setColumnSize(columnWidths, 0);
	}

	protected Comparator<T> createComparator(String... propertyNames) {
		return new PropertyComparator<T>(getObjectClazz(), propertyNames);
	}

	protected Map<String, String> getColumnPropertyMapping() {
		return columnPropertyMapping;
	}

	public String getPropertyName(String columnName) {
		if (getColumnPropertyMapping() != null) {
			return getColumnPropertyMapping().get(columnName);
		} else {
			return null;
		}
	}

	protected Map<String, Boolean> getSortOrder() {
		return sortOrder;
	}

	protected Class<T> getObjectClazz() {
		return objectClazz;
	}

	protected Map<String, Format> getColumnFormatMapping() {
		return columnFormatMapping;
	}

	public long getDataSelectedTime() {
		return dataSelectedTime;
	}

	public void setDataSelectedTime(long dataSelectedTime) {
		this.dataSelectedTime = dataSelectedTime;
	}
}
